# Copyright 2008 Gee-Hsien Chuang
# GNU General Public License v3
# http://www.gnu.org/licenses/gpl.html
#

import string
import base64
import urllib

from xml.dom import minidom, Node
from iknow import iknowconf
from google.appengine.api import urlfetch

def xml_parse(url,method="GET",form_fields={},auth_header=''):
	if method == "GET": 
		result = urlfetch.fetch(url)
	elif method == "POST":
		result = urlfetch.fetch(
			url = url,
			payload = urllib.urlencode(form_fields),
			method = urlfetch.POST,
			headers = {	'Content-Type': 'application/x-www-form-urlencoded',
						'Authorization': auth_header})
	elif method == "DELETE":
		result = urlfetch.fetch(
			url = url,
			payload = urllib.urlencode(form_fields),
			method = urlfetch.DELETE,
			headers = {	'Content-Type': 'application/x-www-form-urlencoded',
						'Authorization': auth_header})
	else:
		return None
	
	# Several HTTP codes signify success
	if (result.status_code >= 200) and (result.status_code <= 204):
		try:
			return minidom.parseString(result.content)
		except:
			# if not parse-able, just return content as-is
			return result.content
	else:
		return None

def get_attributes(parent):
	ret = {}
	attrs = parent.attributes
	for attrName in attrs.keys():
		# attribute is a node
		ret[attrName] = attrs.get(attrName).nodeValue
	return ret

def xml_to_array(parent):
	# parent is a node; this returns an array of __
	ret_val = {}
	ret_val["nodeName"] = parent.nodeName
	ret = {}
	if (parent.nodeType == Node.ELEMENT_NODE) or (parent.nodeType == Node.DOCUMENT_NODE):
		if parent.nodeType == Node.ELEMENT_NODE:
			ret["attributes"] = get_attributes(parent)
		# has children?
		children = parent.childNodes
		if (len(children) == 1) and ((children[0].nodeType == Node.TEXT_NODE) or (children[0].nodeType == Node.CDATA_SECTION_NODE) or (children[0].nodeType == Node.COMMENT_NODE)):
			ret["value"] = children[0].nodeValue
		else:
			ret["children"] = []
			for child in children:
				# we are seeing some blank Node.TEXT_NODE entries.
				# make sure nodetype of child is an element (else would have been caught in above IF if we needed text value)
				if (child.nodeType == Node.ELEMENT_NODE):
					ret["children"].append(xml_to_array(child))
	ret_val["nodeValue"] = ret
	return ret_val

def basic_auth(username,password):
	return "Basic " + base64.b64encode(username + ":" + password)

class iKnow:
	def __init__(self):
		self.api_key = iknowconf.api_key
		self.url_base = iknowconf.url_base
	
	# Given an array representing a node, find all immediate children with name childName
	def find_children(arr,childName):
		ret = []
		children = arr["nodeValue"]["children"]
		for child in children:
			if child["nodeName"] == childName:
				ret.append(child)
		return ret
	find_children = staticmethod(find_children)
	
	#############
	# API METHODS
	#############
	
	##############
	### USER CALLS
	
	def user_profile(self,username):
		url = self.url_base + "/users/" + str(username) + ".rdf"
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0].childNodes[1])
	
	def search_users(self,keyword='',user_image='false',skype_enabled='false',age_from='',age_to='',gender='',known_language='',country='',country_subdivision='',page='1',per_page='20'):
		url = self.url_base + "/users/matching/" + str(keyword) + ".xml?"
		url += "user_image=" + user_image + "&skype_enabled=" + skype_enabled + "&age_from=" + age_from + "&age_to=" + age_to
		url += "&gender=" + gender + "&known_language=" + known_language + "&country=" + country + "&country_subdivision=" + country_subdivision
		url += "&page=" + page + "&per_page=" + per_page
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])
		
	def study_results(self,username,dictation=False,page='1',per_page='20'):
		if dictation: call = "dictation"
		else: call = "iknow"
		url = self.url_base + "/users/" + str(username) + "/study_results/" + str(call) + ".xml?page=" + str(page) + "&per_page=" + str(per_page)
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])
	
	def lists_studied(self,username,page='1',per_page='20'):
		url = self.url_base + "/users/" + str(username) + "/lists.xml?page=" + str(page) + "&per_page=" + str(per_page)
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])
	
	def items_studied(self,username,sort='',ascending='',min_progress='',max_progress='',page='1',per_page='20',include_sentences=''):
		url = self.url_base + "/users/" + str(username) + "/items.xml?sort=" + str(sort) + "&ascending=" + str(ascending) + "&min_progress=" + str(min_progress) + "&max_progress=" + str(max_progress) + "&page=" + str(page) + "&per_page=" + str(per_page) + "&include_sentences=" + str(include_sentences)
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])
		
	##############
	### LIST CALLS
		
	def recent_lists(self,created_by='',page='1',per_page='20'):
		url = self.url_base + "/lists.xml?page=" + str(page) + "&per_page=" + str(per_page) + "&created_by=" + str(created_by)
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])
	
	def show_list(self,id,include_items='false',include_sentences='false'):
		url = self.url_base + "/lists/" + str(id) + ".xml?include_items=" + str(include_items) + "&include_sentences=" + str(include_sentences)
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])
		
	def search_lists(self,keyword='',page='1',per_page='20'):
		url = self.url_base + "/lists/matching/" + str(keyword) + ".xml?page=" + str(page) + "&per_page=" + str(per_page)
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])
	
	def create_list(self,username,password,name,description='',language='ja',translation_language='en',type='theme',transcript='',embed='',tags='',media_entity='',author='',author_url='',attribution_license_id='4'):
		url = self.url_base + "/lists"
		auth_header = basic_auth(username,password)
		form_fields = {
			"api_key": self.api_key,
			"list[name]":name,
			"list[description]":description,
			"list[language]":language,
			"list[translation_language]":translation_language,
			"list[type]":type,
			"list[transcript]":transcript,
			"list[embed]":embed,
			"list[tags]":tags,
			"attribution[media_entity]":media_entity,
			"attribution[author]":author,
			"attribution[author_url]":author_url,
			"attribution[attribution_license_id]":attribution_license_id
		}
		dom = xml_parse(url,"POST",form_fields,auth_header)
		return dom
	
	def delete_list(self,username,password,list_id):
		url = self.url_base + "/lists/" + str(list_id)
		auth_header = basic_auth(username,password)
		form_fields = {
			"api_key": self.api_key,
			"id": list_id
		}
		return xml_parse(url,"DELETE",form_fields,auth_header)
		
	def add_item(self,username,password,list_id,item_id):
		url = self.url_base + "/lists/" + str(list_id) + "/items"
		auth_header = basic_auth(username,password)
		form_fields = {
			"api_key": self.api_key,
			"id": item_id
		}
		return xml_parse(url,"POST",form_fields,auth_header)
	
	def remove_item(self,username,password,list_id,item_id):
		url = self.url_base + "/lists/" + str(list_id) + "/items/" + str(item_id)
		auth_header = basic_auth(username,password)
		form_fields = {
			"api_key": self.api_key,
			"id": item_id
		}
		return xml_parse(url,"DELETE",form_fields,auth_header)
	
	##############
	### ITEM CALLS
	
	def recent_items(self,created_by='',page='1',per_page='20',include_sentences='false'):
		url = self.url_base + "/items.xml?created_by=" + str(created_by) + "&page=" + str(page) + "&per_page=" + str(per_page) + "&include_sentences=" + str(include_sentences)
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])

	def show_item(self,item_id):
		url = self.url_base + "/items/" + str(item_id) + ".xml"
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])

	def search_items(self,keyword='',context='',part_of_speech='',language='en',translation_language='en',page='1',per_page='20',include_sentences='false'):
		url = self.url_base + "/items/matching/" + str(keyword) + ".xml?context=" + str(context) + "&part_of_speech=" + str(part_of_speech) + "&language=" + str(language) + "&translation_language=" + str(translation_language) + "&page=" + str(page) + "&per_page=" + str(per_page) + "&include_sentences=" + str(include_sentences)
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])
		
	def items_in_list(self,list_id,page='1',per_page='20',include_sentences='false'):
		url = self.url_base + "/lists/" + str(list_id) + "/items.xml?page=" + str(page) + "&per_page=" + str(per_page) + "&include_sentences=" + str(include_sentences)
		dom = xml_parse(url)
		return xml_to_array(dom.childNodes[0])
	
	def create_item(self,username,password,cue_text,cue_language,cue_part_of_speech,resp_text,resp_language,char_resp_text='',list_id=''):
		url = self.url_base + "/items"
		auth_header = basic_auth(username,password)
		form_fields = {
			"api_key": self.api_key,
			"cue[text]": cue_text,
			"cue[language]": cue_language,
			"cue[part_of_speech]": cue_part_of_speech,
			"response[text]": resp_text,
			"response[language]": resp_language,
			"character_response[text]": char_resp_text,
			"list_id": list_id
		}
		return xml_parse(url,"POST",form_fields,auth_header)
	
	def add_image(self,username,password,item_id,image_url,image_list_id='',att_media_entity='',att_author='',att_author_url='',att_license_id='4'):
		url = self.url_base + "/items/" + str(item_id) + "/images"
		auth_header = basic_auth(username,password)
		form_fields = {
			"api_key": self.api_key,
			"image[url]": image_url,
			"image[list_id]": image_list_id,
			"attribution[media_entity]": att_media_entity,
			"attribution[author]": att_author,
			"attribution[author_url]": att_author_url,
			"attribution[attribution_license_id]": att_license_id
		}
		return xml_parse(url,"POST",form_fields,auth_header)
	
	def add_sound(self,username,password,item_id,sound_url,sound_list_id='',att_media_entity='',att_author='',att_author_url='',att_license_id='4'):
		url = self.url_base + "/items/" + str(item_id) + "/sounds"
		auth_header = basic_auth(username,password)
		form_fields = {
			"api_key": self.api_key,
			"sound[url]": sound_url,
			"sound[list_id]": sound_list_id,
			"attribution[media_entity]": att_media_entity,
			"attribution[author]": att_author,
			"attribution[author_url]": att_author_url,
			"attribution[attribution_license_id]": att_license_id
		}
		return xml_parse(url,"POST",form_fields,auth_header)
	
	def tag_item(self,username,password,item_id,semantic_tags_names,semantic_tags_disambiguations=[]):
		url = self.url_base + "/items/" + str(item_id) + "/tags"
		auth_header = basic_auth(username,password)
		form_fields = {
			"api_key": self.api_key
		}
		count = 0
		for semantic_tags_name in semantic_tags_names:
			form_fields["semantic_tags["+str(count)+"][name]"] = semantic_tags_name
			disambiguation = semantic_tags_disambiguations[count]
			if disambiguation:
				form_fields["semantic_tags["+str(count)+"][disambiguation]"] = disambiguation
			count += 1
		return xml_parse(url,"POST",form_fields,auth_header)
	
	def untag_item(self,username,password,item_id,semantic_tags_names,semantic_tags_disambiguations=[]):
			url = self.url_base + "/items/" + str(item_id) + "/tags"
			auth_header = basic_auth(username,password)
			form_fields = {
				"api_key": self.api_key
			}
			count = 0
			for semantic_tags_name in semantic_tags_names:
				form_fields["semantic_tags["+str(count)+"][name]"] = semantic_tags_name
				disambiguation = semantic_tags_disambiguations[count]
				if disambiguation:
					form_fields["semantic_tags["+str(count)+"][disambiguation]"] = disambiguation
				count += 1
			return xml_parse(url,"DELETE",form_fields,auth_header)
	
	##############
	### SENTENCE CALLS
	
	# To do.